/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: ipc_packet.cpp
 *
 * Purpose: implementation a parser and budiler for ipc data packet
 *
 * Developer:
 *   wen.gu , 2021-10-15
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/

#include "ipc_packet.h"

#define LOG_TAG "ipcp"
#include "icpp/core/log.h"

namespace icpp
{
namespace com
{
/******************************************************************************
 **    MACROS
 ******************************************************************************/

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
IpcPacketParser::IpcPacketParser(DataReceiveHandler on_receive, ClientId client_id /*= INVALID_CLIENT_ID*/)
    :client_id_(client_id_),
    on_receive_(on_receive)
{
    /** todo something */
}

IpcPacketParser::~IpcPacketParser()
{
    /** todo something */
}


core::IcppErrc IpcPacketParser::fillData(const uint8_t* data, uint32_t size)
{
    const uint8_t* data_ptr = (uint8_t*)data;
    const uint8_t* end_ptr = data + size;
    //LOGD("===>: size: %d\n", size);
    while (data_ptr < end_ptr)
    {
        switch (parse_state_)
        {
        case IpcDataParseState::Start:
            //LOGD("===>: size: %d, start: 0x%x\n", size, *data_ptr);
            data_ptr = onParseStart(data_ptr, end_ptr);
            break;
        case IpcDataParseState::Header:
            //LOGD("===>: size: %d, start: 0x%x\n", size, *data_ptr);
            data_ptr = onParseHeader(data_ptr, end_ptr);
            break; 
        case IpcDataParseState::Payload:
            data_ptr = onParsePayload(data_ptr, end_ptr);
            //LOGD("===>: size: %d\n", size);
            break;
        case IpcDataParseState::End:
            data_ptr = onParseEnd(data_ptr, end_ptr);
            //LOGD("===>: size: %d\n", size);
            break;
        default:
            //LOGD("===>: size: %d\n", size);
            return core::IcppErrc::InvalidStatus;
            break;
        }
    }

    return core::IcppErrc::OK; /** todo, refineme */
    //LOGD("===>: size: %d\n", size);   
}

const uint8_t* IpcPacketParser::onParseStart(const uint8_t* data_ptr, const uint8_t* end_ptr)
{
    while (data_ptr < end_ptr)
    {
        if (*data_ptr == IPC_PKT_START_CODE)
        {
            data_ptr++;

            //ici.readPos = 0;
            header_read_pos_ = 0;
            msg_ptr_ = std::make_shared<Message>();
            parse_state_ = IpcDataParseState::Header;
            break;
        }

        data_ptr++;
    }

    return data_ptr;
}

const uint8_t* IpcPacketParser::onParseHeader(const uint8_t* data_ptr, const uint8_t* end_ptr)
{
    int32_t size = (int32_t)(end_ptr - data_ptr);
    if (header_read_pos_ < IPC_PKT_HDR_SIZE)
    {
        
        int32_t cpSize = IPC_PKT_HDR_SIZE - header_read_pos_;
        cpSize = (cpSize <= size) ? cpSize : size;
        memcpy(&(header_buf_[header_read_pos_]), data_ptr, cpSize);
        header_read_pos_ += cpSize;
        data_ptr += cpSize;
        size -= cpSize;
    }

    if (header_read_pos_ == IPC_PKT_HDR_SIZE)
    {/** parse header data */
        ServiceId srv_id;
        MessageId msg_id;
        uint32_t length;
        ClientId client_id;
        SessionId session_id;
        uint8_t protocol_version;
        uint8_t interface_version;
        MessageType msg_type;
        core::IcppErrc err_code;
        
        uint32_t parse_pos = 0;
        uint8_t* parse_buf = header_buf_;
        memcpy(&srv_id, parse_buf, sizeof(srv_id));
        parse_buf += sizeof(srv_id);
        
        memcpy(&msg_id, parse_buf, sizeof(msg_id));
        parse_buf += sizeof(msg_id);

        memcpy(&length, parse_buf, sizeof(length)); /** the length include: client id|session id|protocol version|interface version| message type| error code|payload size| */
        parse_buf += sizeof(length);

        if (length < IPC_LENGTH_MIN)
        {/** error length */
            parse_state_ = IpcDataParseState::Start;
            return data_ptr; /** todo refine me?? */
        }

        memcpy(&client_id, parse_buf, sizeof(client_id));
        parse_buf += sizeof(client_id);

        memcpy(&session_id, parse_buf, sizeof(session_id));
        parse_buf += sizeof(session_id);

        protocol_version = *parse_buf++;
        interface_version = *parse_buf++;
        msg_type = (MessageType)(*parse_buf++);
        err_code = (core::IcppErrc)(*parse_buf);

        msg_ptr_->set_service_id(srv_id);
        msg_ptr_->set_message_id(msg_id);
        msg_ptr_->set_client_id(client_id);
        msg_ptr_->set_session_id(session_id);
        msg_ptr_->set_protocol_version(protocol_version);
        msg_ptr_->set_interface_version(interface_version);
        msg_ptr_->set_type(msg_type);
        msg_ptr_->set_error_code(err_code);

        if (length > IPC_LENGTH_MIN)
        {
            payload_read_pos_ = 0;
            payload_size_ = length - IPC_LENGTH_MIN;
            payload_ptr_ = std::make_shared<Payload>(payload_size_);
            parse_state_ = IpcDataParseState::Payload;            
        }
        else /** this case is: length == IPC_LENGTH_MIN */
        {
            parse_state_ = IpcDataParseState::End;
        }
    }

    return data_ptr;
}

const uint8_t* IpcPacketParser::onParsePayload(const uint8_t* data_ptr, const uint8_t* end_ptr)
{
    if (payload_read_pos_ < payload_size_)
    {   
        int32_t size = (int32_t)(end_ptr - data_ptr);
        int32_t cpSize = (int32_t)(payload_size_ - payload_read_pos_);
        cpSize = (cpSize <= size) ? cpSize : size;
        memcpy(payload_ptr_->data() + payload_read_pos_, data_ptr, cpSize);
        payload_read_pos_ += cpSize;
        data_ptr += cpSize;
    }

    if (payload_read_pos_ == payload_size_)
    {
        parse_state_ = IpcDataParseState::End;
    }

    return data_ptr;
}

const uint8_t* IpcPacketParser::onParseEnd(const uint8_t* data_ptr, const uint8_t* end_ptr)
{
    //LOGD("===>: size: %d\n", size);
    if (*data_ptr == IPC_PKT_END_CODE)
    {
        data_ptr++;
        if (msg_ptr_)
        {
            msg_ptr_->set_payload(payload_ptr_);

            if (on_receive_)
            {
                if ((msg_ptr_->client_id() == INVALID_CLIENT_ID) && (client_id_ != INVALID_CLIENT_ID))
                {
                    msg_ptr_->set_client_id(client_id_);
                }
                on_receive_(msg_ptr_);
            }
        }

        msg_ptr_ = nullptr;
        payload_ptr_ = nullptr;
    }
    //LOGD("===>: size: %d\n", size);

    parse_state_ = IpcDataParseState::Start;
    return data_ptr;
}



//static
PayloadPtr IpcPacketBuilder::MakeIpcPacket(Message::MessagePtr msg_ptr)
{
    if (!msg_ptr)
    {
        return nullptr;
    }

    uint32_t payload_size  = msg_ptr->payload_size();
    uint32_t pkt_size      = IPC_PKT_HDR_SIZE + payload_size + 1; /** +1: 1 byte end code */
    PayloadPtr payload_ptr = std::make_shared<Payload>(pkt_size);

    if (payload_ptr)
    {
        uint8_t* data_ptr = payload_ptr->data();
        /** |start code|service id|message id|length|client id|session id|protocol version|interface version|type|error code|payload|end code| */        
        ServiceId srv_id = msg_ptr->service_id();
        MessageId msg_id = msg_ptr->message_id();
        uint32_t length  = IPC_LENGTH_MIN + payload_size;
        ClientId client_id   = msg_ptr->client_id();
        SessionId session_id = msg_ptr->session_id();

        *data_ptr++ = IPC_PKT_START_CODE;
        memcpy(data_ptr, &srv_id, sizeof(ServiceId));
        data_ptr += sizeof(ServiceId);
        memcpy(data_ptr, &msg_id, sizeof(msg_id));
        data_ptr += sizeof(msg_id);
        memcpy(data_ptr, &length, sizeof(length));
        data_ptr += sizeof(length);
        memcpy(data_ptr, &client_id, sizeof(client_id));
        data_ptr += sizeof(client_id);
        memcpy(data_ptr, &session_id, sizeof(session_id));
        *data_ptr++ = msg_ptr->protocol_version();
        *data_ptr++ = msg_ptr->interface_version();
        *data_ptr++ = (uint8_t)msg_ptr->type();
        *data_ptr++ = (uint8_t)msg_ptr->error_code();
        if (payload_size > 0)
        {
            memcpy(data_ptr, msg_ptr->payload()->data(), payload_size);
            data_ptr += payload_size;
        }

        *data_ptr = IPC_PKT_END_CODE;
    }

    return payload_ptr;
}


} /** namespace com */
} /** namespace icpp */


